package com.njaccp.dom;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;

public class MyXml {
    public static void main(String[] args) {
        //设置文件路径
        //String filePath= "C:\\Users\\Huyang\\IdeaProjects\\MySecond2\\src\\main\\resources\\bookstore.xml";
        String filePath=System.getProperty("user.dir").concat("\\src\\main\\resources\\bookstore.xml");
        Document dom=null;

        //获取dom树
        dom=MyXml.parseXML(filePath);

        //遍历打印dom树
        System.out.println("*************初始状态的DOM树*************");
        MyXml.traverseDOM(dom);
        System.out.println("****************************************");

        //向dom树中添加元素
        Element book=MyXml.newElement(dom);//方法中创建了一个新元素：《Think in Java》 ,by.Bruce Eckel
        MyXml.addElement(dom,book);

        //遍历打印添加元素后的dom树
        System.out.println("*************添加元素后的DOM树***********");
        MyXml.traverseDOM(dom);
        System.out.println("****************************************");

        //保存添加元素后的新Dom树到XML文件
        MyXml.saveXML(dom,filePath);
    }

    /**
     * 通过文件路径解析XML文件，并返回一个Document类的DOM树
     * @param filePath
     * @return
     */
    public static Document parseXML(String filePath){
        //文档构建工厂
        DocumentBuilderFactory factory=null;
        //文档构建器
        DocumentBuilder documentBuilder=null;
        //文档
        Document document=null;
        //文档的根元素结点
        Element root=null;
        try {
            //创建文档构建工厂，由类的静态方法newInstance创建实例
            factory= DocumentBuilderFactory.newInstance();
            //由工厂创建文档构造器
            documentBuilder=factory.newDocumentBuilder();
            //文档构造器解析指定路径的xml文档，并返回Document类对象
            document=documentBuilder.parse(filePath);
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            return document;
        }
    }

    /**
     * 获取DOM树的根节点，调用递归遍历打印方法traverseFun
     * @param dom XML文件生成DOM树
     */
    public static void traverseDOM(Document dom){
        Element root=dom.getDocumentElement();//获取文档的根元素结点
        traverseFun(root,0);
    }

    /**
     * 递归遍历DOM树并打印树的信息
     * @param element 元素结点
     * @param deep  树的深度，控制缩进
     */
    public static void traverseFun(Element element,int deep){
        for (int i = 0; i < deep; i++) {
            System.out.print("\t");//打印制表符，形成缩进层次
        }
        //打印当前节点名称
        System.out.print(element.getNodeName());
        //打印当前节点的属性
        if(element.hasAttributes()){
            System.out.print("["+element.getAttributes().item(0).getNodeValue()+"]");
        }
        //获得当前节点的子结点表
        NodeList nodeList=element.getChildNodes();
        //如果仅包含一个结点且该结点类型为文本,则打印该文本
        if(nodeList.getLength()==1&&nodeList.item(0).getNodeType()==Node.TEXT_NODE) {
            System.out.println(":"+element.getTextContent());
        }
        else {//否则，遍历子节点
            System.out.println();//换行输出子节点信息
            for (int i = 0; i < nodeList.getLength(); i++) {//循环访问子节点表
                if (nodeList.item(i).getNodeType() == Node.ELEMENT_NODE) {//如果是元素结点，继续遍历子节点
                    traverseFun((Element)nodeList.item(i), deep + 1);//递归调用
                }
            }
        }
    }

    /**
     * 创建新的元素结点，并返回该元素结点
     * 通过DOM树的createElement方法创建Element实例
     * @param dom
     * @return
     */
    public static Element newElement(Document dom){
        //使用Document实例的方法createElement创建元素
        Element book=dom.createElement("book");
        Element title=dom.createElement("title");
        Element author=dom.createElement("author");
        Element year=dom.createElement("year");
        Element price=dom.createElement("price");
        //赋予属性
        book.setAttribute("category","JAVA");
        title.setAttribute("lang","en");
        //设置文本内容
        title.setTextContent("Think in Java");
        author.setTextContent("Bruce Eckel");
        year.setTextContent("2007");
        price.setTextContent("89.00");
        //链接
        book.appendChild(title);
        book.appendChild(author);
        book.appendChild(year);
        book.appendChild(price);
        return book;
    }

    /**
     * 向DOM树中添加一个结点
     * 通过调用文档根元素结点的appendChild方法
     * @param dom
     * @param newElement 需要添加的结点
     */
    public static void addElement(Document dom,Element newElement){
        //1.获取文档根元素
        //2.为根元素结点追加一个子结点
        dom.getDocumentElement().appendChild(newElement);
    }

    /**
     * 将新的DOM树写入到filePath路径
     * 通过转换工厂/设置工厂间隔数量属性/转换器/定义转换器转换格式/允许转换器缩进
     * 需要将Document类的DOM树封装为DOMSource类作为输入源
     * 需要将filePath路径作为参数通过OutputStream和OutputStreamWriter的处理封装为StreamResult作为输出目标
     * @param dom
     * @param filePath  输出的路径及文件名
     */
    public static void saveXML(Document dom,String filePath){
        //创建转换工厂
        TransformerFactory factory=TransformerFactory.newInstance();
        //设置工厂处理的缩进数属性,缩进4个字符
        factory.setAttribute("indent-number",2);
        //创建输出流
        OutputStream os=null;
        OutputStreamWriter osw=null;
        try {
            //由工厂构建转换器
            Transformer transformer=factory.newTransformer();
            //设置转换器的转换格式
            transformer.setOutputProperty(OutputKeys.ENCODING,"UTF-8");
            //允许转换器进行缩进
            transformer.setOutputProperty(OutputKeys.INDENT,"YES");
            //创建转换器的DOM源
            DOMSource domSource=new DOMSource(dom);
            //创建转换器的输出目标
            os=new FileOutputStream(filePath);
            osw=new OutputStreamWriter(os,"UTF-8");
            StreamResult streamResult=new StreamResult(osw);
            //进行转换
            transformer.transform(domSource,streamResult);
        } catch(IOException e){
            e.printStackTrace();
        } catch (TransformerConfigurationException e) {
            e.printStackTrace();
        } catch (TransformerException e){
            e.printStackTrace();
        }finally {
            try {
                osw.close();
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
